# Copyright 2012-2015 Free Software Foundation, Inc.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Contributed by Mentor Graphics, written by Maciej W. Rozycki.

# Test MIPS16 thunk support.

# This should work on any targets that support MIPS16 execution, including
# Linux and bare-iron ones, but not all of them do, for example MIPS16
# support has been added to Linux relatively late in the game.  Also besides
# environment support, the target processor has to support the MIPS16 ASE.
# Finally as of this writing MIPS16 support has only been implemented in the
# toolchain for a subset of ABIs, so we need to check that a MIPS16
# executable can be built and run at all before we attempt the actual test.

if { ![istarget "mips*-*-*"] } then {
    verbose "Skipping MIPS16 thunk support tests."
    return
}

# A helper to set caller's SRCFILE and OBJFILE based on FILENAME and SUFFIX.
proc set_src_and_obj { filename { suffix "" } } {
    upvar srcfile srcfile
    upvar objfile objfile
    global srcdir
    global objdir
    global subdir

    if ![string equal "$suffix" ""] then {
	set suffix "-$suffix"
    }
    set srcfile ${srcdir}/${subdir}/${filename}.c
    set objfile ${objdir}/${subdir}/${filename}${suffix}.o
}

# First check if a trivial MIPS16 program can be built and debugged.  This
# verifies environment and processor support, any failure here must be
# classed as the lack of support.
set testname mips16-thunks-main

set_src_and_obj mips16-thunks-inmain
set options [list debug nowarnings additional_flags=-mips16]
set objfiles ${objfile}
gdb_compile ${srcfile} ${objfile} object ${options}

set_src_and_obj mips16-thunks-main
set options [list debug nowarnings additional_flags=-mips16]
lappend objfiles ${objfile}
gdb_compile ${srcfile} ${objfile} object ${options}

set binfile ${objdir}/${subdir}/${testname}
set options [list debug nowarnings]
if { [gdb_compile ${objfiles} ${binfile} executable ${options}] != "" } then {
    unsupported "No MIPS16 support in the toolchain."
    return
}
clean_restart ${testname}
gdb_breakpoint inmain
gdb_run_cmd
gdb_test_multiple "" "check for MIPS16 support in the processor" {
    -re "Breakpoint 1.*inmain .*$gdb_prompt $" {
	gdb_test_multiple "finish" \
	    "check for MIPS16 support in the processor" {
	    -re "Value returned is \\\$\[0-9\]+ = 0\[^0-9\].*$gdb_prompt $" {
		verbose "MIPS16 support check successful."
	    }
	    -re "$gdb_prompt $" {
		unsupported "No MIPS16 support in the processor."
		return
	    }
	    default {
		unsupported "No MIPS16 support in the processor."
		return
	    }
	}
    }
    -re "$gdb_prompt $" {
	unsupported "No MIPS16 support in the processor."
	return
    }
    default {
	unsupported "No MIPS16 support in the processor."
	return
    }
}

# Check if MIPS16 PIC code can be built and debugged.  We want to check
# PIC and MIPS16 thunks are handled correctly together if possible, but
# on targets that do not support PIC code, e.g. bare iron, we still want
# to test the rest of functionality.
set testname mips16-thunks-pic
set picflag ""

set_src_and_obj mips16-thunks-inmain pic
set options [list \
    debug nowarnings additional_flags=-mips16 additional_flags=-fPIC]
set objfiles ${objfile}
gdb_compile ${srcfile} ${objfile} object ${options}

set_src_and_obj mips16-thunks-main pic
set options [list \
    debug nowarnings additional_flags=-mips16 additional_flags=-fPIC]
lappend objfiles ${objfile}
gdb_compile ${srcfile} ${objfile} object ${options}

set binfile ${objdir}/${subdir}/${testname}
set options [list debug nowarnings additional_flags=-fPIC]
if { [gdb_compile ${objfiles} ${binfile} executable ${options}] == "" } then {
    clean_restart ${testname}
    gdb_breakpoint inmain
    gdb_run_cmd
    gdb_test_multiple "" "check for PIC support" {
	-re "Breakpoint 1.*inmain .*$gdb_prompt $" {
	    note "PIC support present, will make additional PIC thunk checks."
	    set picflag additional_flags=-fPIC
	}
	-re "$gdb_prompt $" {
	    note "No PIC support, skipping additional PIC thunk checks."
	}
	default {
	    note "No PIC support, skipping additional PIC thunk checks."
	}
    }
} else {
    note "No PIC support, skipping additional PIC thunk checks."
}

# OK, build the twisted executable.  This program contains the following
# MIPS16 thunks:
# - __call_stub_fp_sin,
# - __call_stub_fp_sinblah,
# - __call_stub_fp_sinfrob,
# - __call_stub_fp_sinhelper,
# - __call_stub_lsinhelper,
# - __fn_stub_lsinmips16,
# - __fn_stub_sinblah16,
# - __fn_stub_sinfrob16,
# - __fn_stub_sinmips16,
# - __mips16_call_stub_df_2,
# - __mips16_ret_df.
# Additionally, if PIC code is supported, it contains the following PIC thunks:
# - .pic.__mips16_call_stub_df_2,
# - .pic.__mips16_ret_df,
# - .pic.sinblah,
# - .pic.sinblah16,
# - .pic.sinfrob,
# - .pic.sinfrob16.
set testname mips16-thunks-sin

set_src_and_obj mips16-thunks-sinmain
set options [list debug nowarnings additional_flags=-mips16]
set objfiles ${objfile}
gdb_compile ${srcfile} ${objfile} object ${options}

set_src_and_obj mips16-thunks-sin
set options [list debug nowarnings additional_flags=-mno-mips16]
lappend objfiles ${objfile}
gdb_compile ${srcfile} ${objfile} object ${options}

set_src_and_obj mips16-thunks-sinmips16
set options [list debug nowarnings additional_flags=-mips16]
lappend objfiles ${objfile}
gdb_compile ${srcfile} ${objfile} object ${options}

set_src_and_obj mips16-thunks-sinfrob
set options [list \
    debug nowarnings additional_flags=-mno-mips16 ${picflag}]
lappend objfiles ${objfile}
gdb_compile ${srcfile} ${objfile} object ${options}

set_src_and_obj mips16-thunks-sinfrob16
set options [list \
    debug nowarnings additional_flags=-mips16 ${picflag}]
lappend objfiles ${objfile}
gdb_compile ${srcfile} ${objfile} object ${options}

set binfile ${objdir}/${subdir}/${testname}
set options [list debug nowarnings]
gdb_compile ${objfiles} ${binfile} executable ${options}
clean_restart ${testname}
if ![runto_main] then {
    fail "running test program, MIPS16 thunk tests aborted"
    return
}

# Build some useful regular expressions out of a list of functions FUNCS
# to be used to match against backtraces.
proc build_frames_re { funcs } {
    upvar anyframe anyframe
    upvar frames frames
    upvar frame frame
    upvar func func

    set fid 0
    set argsandsource " +\\\(.*\\\) +at +\[^\r\n\]+\r\n"
    set addrin "(?:\[^ \]+ +in +)?"
    set anyframe "#${fid} +${addrin}(\[^ \]+)${argsandsource}"
    set frame "#${fid} +${addrin}${func}${argsandsource}"
    set frames "$frame"
    foreach f [lrange $funcs 1 end] {
	incr fid
	append frames "#${fid} +${addrin}${f}${argsandsource}"
    }
}

# Single-step through the function that is at the head of function list
# FUNCS until a different function (frame) is reached.  Before each step
# check the backtrace against FUNCS.  ID is used for reporting, to tell
# apart different calls to this procedure for the same function.  If
# successful, then return the name of the function we have stopped in.
proc step_through { id funcs } {
    global gdb_prompt

    set func [lindex $funcs 0]
    build_frames_re "$funcs"

    set msg "single-stepping through \"${func}\" ($id)"

    # Arbitrarily limit the maximium number of steps made to avoid looping
    # indefinitely in the case something goes wrong, increase as (if)
    # necessary.
    set count 8
    while { $count > 0 } {
	if { [gdb_test_multiple "backtrace" "$msg (backtrace)" {
	    -re "${frames}$gdb_prompt $" {
		if { [gdb_test_multiple "step" "$msg (step)" {
		    -re "$gdb_prompt $" {
			if { [gdb_test_multiple "frame" "$msg (frame)" {
			    -re "${frame}.*$gdb_prompt $" {
			    }
			    -re "${anyframe}.*$gdb_prompt $" {
				pass "$msg"
				return $expect_out(1,string)
			    }
			}] != 0 } then {
			    return ""
			}
		    }
		}] != 0 } then {
		    return ""
		}
	    }
	}] != 0 } then {
	    return ""
	}
	incr count -1
    }
    fail "$msg (too many steps)"
    return ""
}

# Finish the current function that must be one that is at the head of
# function list FUNCS.  Before that check the backtrace against FUNCS.
# ID is used for reporting, to tell apart different calls to this
# procedure for the same function.  If successful, then return the name
# of the function we have stopped in.
proc finish_through { id funcs } {
    global gdb_prompt

    set func [lindex $funcs 0]
    build_frames_re "$funcs"

    set msg "finishing \"${func}\" ($id)"

    gdb_test_multiple "backtrace" "$msg (backtrace)" {
	-re "${frames}$gdb_prompt $" {
	    gdb_test_multiple "finish" "$msg (finish)" {
		-re "Run till exit from ${frame}.*$gdb_prompt $" {
		    gdb_test_multiple "frame" "$msg (frame)" {
			-re "${anyframe}.*$gdb_prompt $" {
			    pass "$msg"
			    return $expect_out(1,string)
			}
		    }
		}
	    }
	}
    }
    return ""
}

# Report PASS if VAL is equal to EXP, otherwise report FAIL, using MSG.
proc pass_if_eq { val exp msg } {
    if [string equal "$val" "$exp"] then {
	pass "$msg"
    } else {
	fail "$msg"
    }
}

# Check if FUNC is equal to WANT.  If not, then assume that we have stepped
# into a library call.  In this case finish it, then step out of the caller.
# ID is used for reporting, to tell apart different calls to this procedure
# for the same function.  If successful, then return the name of the
# function we have stopped in.
proc finish_if_ne { id func want funcs } {
    if ![string equal "$func" "$want"] then {
	set call "$func"
	set want [lindex $funcs 0]
	set func [finish_through "$id" [linsert $funcs 0 "$func"]]
	pass_if_eq "$func" "$want" "\"${call}\" finishing to \"${want}\" ($id)"
	set func [step_through "$id" $funcs]
    }
    return "$func"
}

# Now single-step through the program, making sure all thunks are correctly
# stepped over and omitted from backtraces.

set id 1
set func [step_through $id [list main]]
pass_if_eq "$func" sinfrob16 "stepping from \"main\" into \"sinfrob16\" ($id)"

incr id
set func [step_through $id [list sinfrob16 main]]
set func [finish_if_ne $id "$func" main [list sinfrob16 main]]
pass_if_eq "$func" main "stepping from \"sinfrob16\" back to \"main\" ($id)"

incr id
set func [step_through $id [list main]]
pass_if_eq "$func" sinfrob "stepping from \"main\" into \"sinfrob\" ($id)"

incr id
set func [step_through $id [list sinfrob main]]
set func [finish_if_ne $id "$func" main [list sinfrob main]]
pass_if_eq "$func" main "stepping from \"sinfrob\" back to \"main\" ($id)"

# 5
incr id
set func [step_through $id [list main]]
pass_if_eq "$func" sinhelper "stepping from \"main\" into \"sinhelper\" ($id)"

incr id
set func [step_through $id [list sinhelper main]]
set func [finish_if_ne $id "$func" sinfrob16 [list sinhelper main]]
pass_if_eq "$func" sinfrob16 \
    "stepping from \"sinhelper\" into \"sinfrob16\" ($id)"

incr id
set func [step_through $id [list sinfrob16 sinhelper main]]
set func [finish_if_ne $id "$func" sinhelper [list sinfrob16 sinhelper main]]
pass_if_eq "$func" sinhelper \
    "stepping from \"sinfrob16\" back to \"sinhelper\" ($id)"

incr id
set func [step_through $id [list sinhelper main]]
pass_if_eq "$func" sinfrob "stepping from \"sinhelper\" into \"sinfrob\" ($id)"

incr id
set func [step_through $id [list sinfrob sinhelper main]]
set func [finish_if_ne $id "$func" sinhelper [list sinfrob sinhelper main]]
pass_if_eq "$func" sinhelper \
    "stepping from \"sinfrob\" back to \"sinhelper\" ($id)"

# 10
incr id
set func [step_through $id [list sinhelper main]]
pass_if_eq "$func" sinmips16 \
    "stepping from \"sinhelper\" into \"sinmips16\" ($id)"

incr id
set func [step_through $id [list sinmips16 sinhelper main]]
set func [finish_if_ne $id "$func" sinfrob16 [list sinmips16 sinhelper main]]
pass_if_eq "$func" sinfrob16 \
    "stepping from \"sinmips16\" into \"sinfrob16\" ($id)"

incr id
set func [step_through $id [list sinfrob16 sinmips16 sinhelper main]]
set func [finish_if_ne $id "$func" sinmips16 \
	      [list sinfrob16 sinmips16 sinhelper main]]
pass_if_eq "$func" sinmips16 \
    "stepping from \"sinfrob16\" back to \"sinmips16\" ($id)"

incr id
set func [step_through $id [list sinmips16 sinhelper main]]
pass_if_eq "$func" sinfrob "stepping from \"sinmips16\" into \"sinfrob\" ($id)"

incr id
set func [step_through $id [list sinfrob sinmips16 sinhelper main]]
set func [finish_if_ne $id "$func" sinhelper \
	      [list sinfrob sinmips16 sinhelper main]]
pass_if_eq "$func" sinmips16 \
    "stepping from \"sinfrob\" back to \"sinmips16\" ($id)"

# 15
incr id
set func [step_through $id [list sinmips16 sinhelper main]]
pass_if_eq "$func" sinfrob16 \
    "stepping from \"sinmips16\" into \"sinfrob16\" (indirectly) ($id)"

incr id
set func [step_through $id [list sinfrob16 sinmips16 sinhelper main]]
set func [finish_if_ne $id "$func" sinmips16 \
	      [list sinfrob16 sinmips16 sinhelper main]]
pass_if_eq "$func" sinmips16 \
    "stepping from \"sinfrob16\" back to \"sinmips16\" (indirectly) ($id)"

incr id
set func [step_through $id [list sinmips16 sinhelper main]]
pass_if_eq "$func" sinfrob \
    "stepping from \"sinmips16\" into \"sinfrob\" (indirectly) ($id)"

incr id
set func [step_through $id [list sinfrob sinmips16 sinhelper main]]
set func [finish_if_ne $id "$func" sinhelper \
	      [list sinfrob sinmips16 sinhelper main]]
pass_if_eq "$func" sinmips16 \
    "stepping from \"sinfrob\" back to \"sinmips16\" (indirectly) ($id)"

incr id
set func [step_through $id [list sinmips16 sinhelper main]]
pass_if_eq "$func" sinhelper \
    "stepping from \"sinmips16\" back to \"sinhelper\" ($id)"

# 20
incr id
set func [step_through $id [list sinhelper main]]
pass_if_eq "$func" main "stepping from \"sinhelper\" back to \"main\" ($id)"

incr id
set func [step_through $id [list main]]
pass_if_eq "$func" sinblah "stepping from \"main\" into \"sinblah\" ($id)"

incr id
set func [step_through $id [list sinblah main]]
set func [finish_if_ne $id "$func" main [list sinblah main]]
pass_if_eq "$func" main "stepping from \"sinblah\" back to \"main\" ($id)"

incr id
set func [step_through $id [list main]]
pass_if_eq "$func" sinblah16 "stepping from \"main\" into \"sinblah16\" ($id)"

incr id
set func [step_through $id [list sinblah16 main]]
set func [finish_if_ne $id "$func" main [list sinblah16 main]]
pass_if_eq "$func" main "stepping from \"sinblah16\" back to \"main\" ($id)"

# 25
incr id
set func [step_through $id [list main]]
pass_if_eq "$func" lsinhelper \
    "stepping from \"main\" into \"lsinhelper\" ($id)"

incr id
set func [step_through $id [list lsinhelper main]]
set func [finish_if_ne $id "$func" sinblah [list lsinhelper main]]
pass_if_eq "$func" sinblah \
    "stepping from \"lsinhelper\" into \"sinblah\" ($id)"

incr id
set func [step_through $id [list sinblah lsinhelper main]]
set func [finish_if_ne $id "$func" lsinhelper [list sinblah lsinhelper main]]
pass_if_eq "$func" lsinhelper \
    "stepping from \"sinblah\" back to \"lsinhelper\" ($id)"

incr id
set func [step_through $id [list lsinhelper main]]
pass_if_eq "$func" sinblah16 \
    "stepping from \"lsinhelper\" into \"sinblah16\" ($id)"

incr id
set func [step_through $id [list sinblah16 lsinhelper main]]
set func [finish_if_ne $id "$func" lsinhelper [list sinblah16 lsinhelper main]]
pass_if_eq "$func" lsinhelper \
    "stepping from \"sinblah16\" back to \"lsinhelper\" ($id)"

# 30
incr id
set func [step_through $id [list lsinhelper main]]
pass_if_eq "$func" lsinmips16 \
    "stepping from \"lsinhelper\" into \"lsinmips16\" ($id)"

incr id
set func [step_through $id [list lsinmips16 lsinhelper main]]
set func [finish_if_ne $id "$func" sinblah [list lsinmips16 lsinhelper main]]
pass_if_eq "$func" sinblah \
    "stepping from \"lsinmips16\" into \"sinblah\" ($id)"

incr id
set func [step_through $id [list sinblah lsinmips16 lsinhelper main]]
set func [finish_if_ne $id "$func" lsinmips16 \
	      [list sinblah lsinmips16 lsinhelper main]]
pass_if_eq "$func" lsinmips16 \
    "stepping from \"sinblah\" back to \"lsinmips16\" ($id)"

incr id
set func [step_through $id [list lsinmips16 lsinhelper main]]
pass_if_eq "$func" sinblah16 \
    "stepping from \"lsinmips16\" into \"sinblah16\" ($id)"

incr id
set func [step_through $id [list sinblah16 lsinmips16 lsinhelper main]]
set func [finish_if_ne $id "$func" lsinhelper \
	      [list sinblah16 lsinmips16 lsinhelper main]]
pass_if_eq "$func" lsinmips16 \
    "stepping from \"sinblah16\" back to \"lsinmips16\" ($id)"

# 35
incr id
set func [step_through $id [list lsinmips16 lsinhelper main]]
pass_if_eq "$func" sinblah \
    "stepping from \"lsinmips16\" into \"sinblah\" (indirectly) ($id)"

incr id
set func [step_through $id [list sinblah lsinmips16 lsinhelper main]]
set func [finish_if_ne $id "$func" lsinmips16 \
	      [list sinblah lsinmips16 lsinhelper main]]
pass_if_eq "$func" lsinmips16 \
    "stepping from \"sinblah\" back to \"lsinmips16\" (indirectly) ($id)"

incr id
set func [step_through $id [list lsinmips16 lsinhelper main]]
pass_if_eq "$func" sinblah16 \
    "stepping from \"lsinmips16\" into \"sinblah16\" (indirectly) ($id)"

incr id
set func [step_through $id [list sinblah16 lsinmips16 lsinhelper main]]
set func [finish_if_ne $id "$func" lsinhelper \
	      [list sinblah16 lsinmips16 lsinhelper main]]
pass_if_eq "$func" lsinmips16 \
    "stepping from \"sinblah16\" back to \"lsinmips16\" (indirectly) ($id)"

incr id
set func [step_through $id [list lsinmips16 lsinhelper main]]
pass_if_eq "$func" lsinhelper \
    "stepping from \"lsinmips16\" back to \"lsinhelper\" ($id)"

# 40
incr id
set func [step_through $id [list lsinhelper main]]
pass_if_eq "$func" main "stepping from \"lsinhelper\" back to \"main\" ($id)"
